using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Titanium.XnaGameEngine.SceneEditor
{
    unsafe static class DxtDecompression
    {

        // --------------------------------------------------------------------------------
        // S3TC DXT1 / DXT5 Texture Decompression Routines
        // Author: Benjamin Dobell - http://www.glassechidna.com.au
        //
        // Feel free to use these methods in any open-source, freeware or commercial
        // projects. It's not necessary to credit me however I would be grateful if you
        // chose to do so. I'll also be very interested to hear what projects makes use of
        // this code. Feel free to drop me a line via the contact form on the Glass Echidna
        // website.
        //
        // NOTE: The code was written for a big endian system where sizeof(long) == 4.
        // --------------------------------------------------------------------------------

        /// <summary>
        /// long PackRGBA(): Helper method that packs RGBA channels into a single 4 byte pixel.
        /// </summary>
        /// <param name="r">red channel.</param>
        /// <param name="g">green channel.</param>
        /// <param name="b">blue channel.</param>
        /// <param name="a">alpha channel.</param>
        /// <returns></returns>
        static uint PackRGBA(byte r, byte g, byte b, byte a)
        {
            return (uint)((r << 24) | (g << 16) | (b << 8) | a);
        }

        /// <summary>
        /// Decompresses one block of a DXT1 texture and stores the resulting pixels at the appropriate offset in 'image'.
        /// </summary>
        /// <param name="x">x-coordinate of the first pixel in the block.</param>
        /// <param name="y">y-coordinate of the first pixel in the block.</param>
        /// <param name="width">width of the texture being decompressed.</param>
        /// <param name="blockStorage">pointer to the block to decompress.</param>
        /// <param name="image">array where the decompressed pixel data should be stored.</param>
        static void DecompressBlockDXT1(long x, long y, long width, byte* blockStorage, uint[] image)
        {
            ushort color0 = *(ushort*)blockStorage;
            ushort color1 = *(ushort*)(blockStorage + 2);

            long temp;

            temp = (color0 >> 11) * 255 + 16;
            byte r0 = (byte)((temp / 32 + temp) / 32);
            temp = ((color0 & 0x07E0) >> 5) * 255 + 32;
            byte g0 = (byte)((temp / 64 + temp) / 64);
            temp = (color0 & 0x001F) * 255 + 16;
            byte b0 = (byte)((temp / 32 + temp) / 32);

            temp = (color1 >> 11) * 255 + 16;
            byte r1 = (byte)((temp / 32 + temp) / 32);
            temp = ((color1 & 0x07E0) >> 5) * 255 + 32;
            byte g1 = (byte)((temp / 64 + temp) / 64);
            temp = (color1 & 0x001F) * 255 + 16;
            byte b1 = (byte)((temp / 32 + temp) / 32);

            uint code = *(uint*)(blockStorage + 4);

            for (int j = 0; j < 4; j++)
            {
                for (int i = 0; i < 4; i++)
                {
                    uint finalColor = 0;
                    byte positionCode = (byte)((code >> 2 * (4 * j + i)) & 0x03);

                    if (color0 > color1)
                    {
                        switch ((uint)positionCode)
                        {
                            case 0:
                                finalColor = PackRGBA(r0, g0, b0, 255);
                                break;
                            case 1:
                                finalColor = PackRGBA(r1, g1, b1, 255);
                                break;
                            case 2:
                                finalColor = PackRGBA((byte)((2 * r0 + r1) / 3), (byte)((2 * g0 + g1) / 3), (byte)((2 * b0 + b1) / 3), 255);
                                break;
                            case 3:
                                finalColor = PackRGBA((byte)((r0 + 2 * r1) / 3), (byte)((g0 + 2 * g1) / 3), (byte)((b0 + 2 * b1) / 3), 255);
                                break;
                        }
                    }
                    else
                    {
                        switch (positionCode)
                        {
                            case 0:
                                finalColor = PackRGBA(r0, g0, b0, 255);
                                break;
                            case 1:
                                finalColor = PackRGBA(r1, g1, b1, 255);
                                break;
                            case 2:
                                finalColor = PackRGBA((byte)((r0 + r1) / 2), (byte)((g0 + g1) / 2), (byte)((b0 + b1) / 2), 255);
                                break;
                            case 3:
                                finalColor = PackRGBA(0, 0, 0, 255);
                                break;
                        }
                    }

                    if (x + i < width)
                    {
                        image[(y + j) * width + (x + i)] = (byte)finalColor;  
                    }
                }
            }
        }

        /// <summary>
        /// Decompresses all the blocks of a DXT1 compressed texture and stores the resulting pixels in 'image'.
        /// </summary>
        /// <param name="count">Number of compressed blocks in 'blockStorage'.</param>
        /// <param name="width">Texture height.</param>
        /// <param name="height">Texture height.</param>
        /// <param name="blockStorage">pointer to compressed DXT1 blocks.</param>
        /// <param name="image">array where the decompressed pixels will be stored.</param>
        public static void BlockDecompressImageDXT1(long count, long width, long height, byte* blockStorage, uint[] image)
        {
            long blockCountX = (width + 3) / 4;
            long blockCountY = (height + 3) / 4;

            for (long a = 0; a < count; a++)
            {
                for (long j = 0; j < blockCountY; j++)
                {
                    for (long i = 0; i < blockCountX; i++)
                        DecompressBlockDXT1(i * 4, j * 4, width, blockStorage + i * 8, image);
                    blockStorage += blockCountX * 8;
                }
            }
        }

        // void DecompressBlockDXT5(): Decompresses one block of a DXT5 texture and stores the resulting pixels at the appropriate offset in 'image'.
        //
        // long x:					x-coordinate of the first pixel in the block.
        // long y:					y-coordinate of the first pixel in the block.
        // long width: 				width of the texture being decompressed.
        // long height:				height of the texture being decompressed.
        // byte *blockStorage:	    pointer to the block to decompress.
        // long *image:				pointer to image where the decompressed pixel data should be stored.

        static void DecompressBlockDXT5(long x, long y, long width, byte* blockStorage, uint[] image)
        {
            byte alpha0 = *(blockStorage);
            byte alpha1 = *(blockStorage + 1);

            byte* bits = blockStorage + 2;
            uint alphaCode1 = (uint)(bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24));
            uint alphaCode2 = (uint)(bits[0] | (bits[1] << 8));

            ushort color0 = *(ushort*)(blockStorage + 8);
            ushort color1 = *(ushort*)(blockStorage + 10);

            long temp;

            temp = (color0 >> 11) * 255 + 16;
            byte r0 = (byte)((temp / 32 + temp) / 32);
            temp = ((color0 & 0x07E0) >> 5) * 255 + 32;
            byte g0 = (byte)((temp / 64 + temp) / 64);
            temp = (color0 & 0x001F) * 255 + 16;
            byte b0 = (byte)((temp / 32 + temp) / 32);

            temp = (color1 >> 11) * 255 + 16;
            byte r1 = (byte)((temp / 32 + temp) / 32);
            temp = ((color1 & 0x07E0) >> 5) * 255 + 32;
            byte g1 = (byte)((temp / 64 + temp) / 64);
            temp = (color1 & 0x001F) * 255 + 16;
            byte b1 = (byte)((temp / 32 + temp) / 32);

            uint code = *(uint*)(blockStorage + 12);

            for (int j = 0; j < 4; j++)
            {
                for (int i = 0; i < 4; i++)
                {
                    int alphaCodeIndex = (3 * (4 * j + i));
                    uint alphaCode;

                    if (alphaCodeIndex <= 12)
                    {
                        alphaCode = (uint)((alphaCode2 >> alphaCodeIndex) & 0x07);
                    }
                    else if (alphaCodeIndex == 15)
                    {
                        alphaCode = (uint)((alphaCode2 >> 15) | ((alphaCode1 << 1) & 0x06));
                    }
                    else // alphaCodeIndex >= 18 && alphaCodeIndex <= 45
                    {
                        alphaCode = (alphaCode1 >> (alphaCodeIndex - 16)) & 0x07;
                    }

                    byte finalAlpha;
                    if (alphaCode == 0)
                    {
                        finalAlpha = alpha0;
                    }
                    else if (alphaCode == 1)
                    {
                        finalAlpha = alpha1;
                    }
                    else
                    {
                        if (alpha0 > alpha1)
                        {
                            finalAlpha = (byte)(((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7);
                        }
                        else
                        {
                            if (alphaCode == 6)
                                finalAlpha = 0;
                            else if (alphaCode == 7)
                                finalAlpha = 255;
                            else
                                finalAlpha = (byte)(((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5);
                        }
                    }

                    byte colorCode = (byte)((code >> 2 * (4 * j + i)) & 0x03);

                    uint finalColor = 0;
                    switch (colorCode)
                    {
                        case 0:
                            finalColor = PackRGBA(r0, g0, b0, (byte)finalAlpha);
                            break;
                        case 1:
                            finalColor = PackRGBA(r1, g1, b1, finalAlpha);
                            break;
                        case 2:
                            finalColor = PackRGBA((byte)((2 * r0 + r1) / 3), (byte)((2 * g0 + g1) / 3), (byte)((2 * b0 + b1) / 3), finalAlpha);
                            break;
                        case 3:
                            finalColor = PackRGBA((byte)((r0 + 2 * r1) / 3), (byte)((g0 + 2 * g1) / 3), (byte)((b0 + 2 * b1) / 3), finalAlpha);
                            break;
                    }

                    if (x + i < width)
                        image[(y + j) * width + (x + i)] = (byte)finalColor;  
                }
            }
        }

        // void BlockDecompressImageDXT5(): Decompresses all the blocks of a DXT5 compressed texture and stores the resulting pixels in 'image'.
        //
        // long count:				Number of compressed blocks in 'blockStorage'.
        // long width:				Texture width.
        // long height:				Texture height.
        // byte *blockStorage:	    pointer to compressed DXT1 blocks.
        // long *image:				pointer to the image where the decompressed pixels will be stored.
        public static void BlockDecompressImageDXT5(long count, long width, long height, byte* blockStorage, uint[] image)
        {
            long blockCountX = (width + 3) / 4;
            long blockCountY = (height + 3) / 4;
            long blockWidth = (width < 4) ? width : 4;
            long blockHeight = (height < 4) ? height : 4;

            for (long a = 0; a < count; a++)
            {
                for (long j = 0; j < blockCountY; j++)
                {
                    for (long i = 0; i < blockCountX; i++) DecompressBlockDXT5(i * 4, j * 4, width, blockStorage + i * 16, image);
                    blockStorage += blockCountX * 16;
                }
            }
        }


    }
}
